#pragma once
#include <iostream>
#include <queue>
#include <functional>
#include <pthread.h>


const int defaultcap = 5;

template <typename T>
class BlockQueue {
private:
    bool isFull() {
        return _blockqueue.size() == _max_cap;
    }

    bool isEmpty() {
        return _blockqueue.empty();
    }
public:
    BlockQueue(int cap = defaultcap)
        : _max_cap(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }

    void Pop(T* out) {
        pthread_mutex_lock(&_mutex);
        while (isEmpty()) {
            // 这个时刻容量为空，不能在消费了，必须等待
            // 调用wait函数的时候，处理让自己在这里排队等待，还会释放锁，让其他线程去抢锁
            // 当被唤醒的时候，还是会在wait函数中竞争锁，只有重新抢到锁wait函数才会返回 
            // 添加尚未满足，但是线程被异常唤醒的情况，我们称其为伪唤醒
            pthread_cond_wait(&_c_cond, &_mutex);            
        }
        // 然后从队列中拿出数据
        *out = _blockqueue.front();
        _blockqueue.pop();

        // 不管先解锁还是先唤醒都是一样，都可以达到目的
        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_p_cond);
    }

    void Push(const T& in) {
        // 向内装入数据的时候需要加锁
        pthread_mutex_lock(&_mutex);
        // 这里使用循环是防止唤醒的时候使用的broadcast函数，或者signal函数调用失败
        // 使用该函数之后被唤醒且没有抢到锁的线程会阻塞在抢锁这个位置
        // 当一个刚被唤醒且抢到锁的线程执行结束准备释放锁的时候
        // 会立即被被阻塞在锁这个位置的生产者抢到，这样很可能导致生产的数量
        // 超过容量
        while (isFull()) {
            // 队列满，需要将生产者阻塞，然后唤醒消费者
            pthread_cond_wait(&_p_cond, &_mutex);
        }
        // 现在将我们的任务放入到队列中
        _blockqueue.push(in);

        // 先解锁，然后在唤醒，这样就可以在唤醒之后立刻就拿到锁
        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_c_cond);
    }

    ~BlockQueue() {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }
private:
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond;
    pthread_cond_t _c_cond;
    // 外面不断向内写入数据，内部将数据
    std::queue<T> _blockqueue; // 临界资源
    // 先实现单生产者和单消费者
    // pthread_t _tid;
    int _max_cap;
};